/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package pt.iscte.iul.dcti.pa.gspas.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import pt.iscte.iul.dcti.pa.gspas.gui.annotation.ComponentLabelAnnotation;
import pt.iscte.iul.dcti.pa.gspas.gui.annotation.ModelAnnotation;

/**
 *
 * @author Gedsimon Pereira - 58554
 * @contact gspas@iscte.pt
 */
public class Property {

    private String propertyName;
    private String label;
    private PropertyType type;
    private List<PropertyAccess> access;
    private Class<?> clazz;

    public Property() {
    }

    public Property(String propertyName, String label,
            List<PropertyAccess> access, PropertyType type, Class<?> clazz) {
        this.propertyName = propertyName;
        this.label = label;
        this.access = access;
        this.type = type;
        this.clazz = clazz;
    }

    public String getPropertyName() {
        return propertyName;
    }

    public void setPropertyName(String propertyName) {
        this.propertyName = propertyName;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public List<PropertyAccess> getAccess() {
        return access;
    }

    public void setAccess(List<PropertyAccess> access) {
        this.access = access;
    }

    public Class<?> getClazz() {
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    public PropertyType getType() {
        return type;
    }

    public void setType(PropertyType type) {
        this.type = type;
    }

    public static List<Property> getProperties(Class<?> object) {
        Map<String, List<PropertyAccess>> beanMethods = new HashMap<String, List<PropertyAccess>>();
        Method[] mtds = object.getDeclaredMethods();
        for (Method method : mtds) {
            BeanType beanType;
            if ((beanType = isBeanMethod(object, method)) != null) {
                String key = beanType.getMethodProperty(method.getName());
                if (!beanMethods.containsKey(key)) {
                    beanMethods.put(key, new ArrayList<PropertyAccess>());
                }
                beanMethods.get(key).add(new PropertyAccess(method, beanType));
            }
        }
        List<Property> properties = new ArrayList<Property>();
        for (String propertyName : beanMethods.keySet()) {
            List<PropertyAccess> acesses = beanMethods.get(propertyName);
            String label = componentLabel(object, acesses, propertyName);
            PropertyType type = getPropertyType(acesses) != null ? getPropertyType(acesses)
                    : PropertyType.VOID;
            Class<?> cls = classModel(object, acesses);
            properties
                    .add(new Property(propertyName, label, acesses, type, cls));
        }
        return properties;
    }

    private static PropertyType getPropertyType(List<PropertyAccess> acesses) {
        for (PropertyAccess propertyAccess : acesses) {
            if (propertyAccess.getType().isGetter()) {
                return PropertyType.getPropertyFromClass(propertyAccess
                        .getMethod().getReturnType());
            }
        }
        return null;
    }

    /**
     *
     * @param cls - Class name
     * @param propertyName
     * @param methodName Method name
     * @return label to associate to component
     */
    private static String componentLabel(Class cls,
            List<PropertyAccess> acesses, String propertyName) {
        String cLabel = propertyName;

        for (PropertyAccess propertyAccess : acesses) {
            Method m = propertyAccess.getMethod();
            ComponentLabelAnnotation clAnnotation = m
                    .getAnnotation(ComponentLabelAnnotation.class);
            if (clAnnotation != null) {
                System.out.println(clAnnotation.label());
                cLabel = clAnnotation.label();
                break;
            }
        }
        return cLabel;
    }

    private static Class<?> classModel(Class cls, List<PropertyAccess> acesses) {
        Class<?> retClass = null;

        for (PropertyAccess propertyAccess : acesses) {
            Method m = propertyAccess.getMethod();
            ModelAnnotation mdAnnotation = m
                    .getAnnotation(ModelAnnotation.class);
            if (mdAnnotation != null) {
                retClass = mdAnnotation.clazz();
                break;
            }
        }

        return retClass;
    }

    /**
     *
     * @param object
     * @param beanMethod
     * @return
     */
    private static BeanType isBeanMethod(Class<?> object, Method beanMethod) {
        String beanMethodName = beanMethod.getName();
        BeanType beanMethodType = BeanType.get(beanMethodName);
        String property = beanMethodType.getMethodProperty(beanMethodName);
        if (beanMethodType.isGetter() || beanMethodType.isSetter()) {
            return beanMethodType;
        }
        Method[] mtds = object.getDeclaredMethods();
        for (Method otherMethod : mtds) {
            String otherMethodName = otherMethod.getName();
            BeanType otherMethodType = BeanType.get(otherMethodName);

            if (beanMethodType.isAdd()
                    && otherMethodType.isGetter()
                    && property.equals(beanMethodType
                            .getMethodProperty(beanMethodName))) {
                Class<?> otherBeanReturnType = otherMethod.getReturnType();

                if (Collection.class.isAssignableFrom(otherBeanReturnType)) {
                    return beanMethodType;
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public <T> T getValue(Object object, Class<T> inputClass) {
        if (getGetter() != null) {
            try {
                return (T) getGetter().getMethod().invoke(object,
                        new Object[]{});
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private PropertyAccess getGetter() {
        for (PropertyAccess pA : access) {
            if (pA.getType().isGetter()) {
                return pA;
            }
        }
        return null;
    }

    public List<Property> getAddArgumentsProperties() {
        return getAdd() != null ? getAdd().getArgumentsProperties() : null;
    }

    private PropertyAccess getAdd() {
        for (PropertyAccess pA : access) {
            if (pA.getType().isAdd()) {
                return pA;
            }
        }
        return null;
    }

    public void callAdd(Object object, Object... args) {
        if (getAdd() != null) {
            try {
                getAdd().getMethod().invoke(object, args);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}
